package org.zoomdy.gdbserver.extender;

import java.io.*;
import java.util.*;
import java.util.concurrent.locks.*;

public class eCos extends Node
{
    public eCos()
    {
        lock = new ReentrantLock();
        condition = lock.newCondition();
    }
    
    public boolean setProperty(String name, String value)
    {
        if(name.equals("verbose"))
        {
            if(value.equals("1") || value.equals("true"))
            {
                this.verbose = true;
            }
            else
            {
                this.verbose = false;
            }
            
            return true;
        }
        return false;
    }
    
    public String getProperty(String name)
    {
        if(name.equals("verbose"))
        {
            if(this.verbose)
            {
                return "true";
            }
            else
            {
                return "false";
            }
        }
        return null;
    }
    
    public boolean connect()
    {
        symbolIndex = 0;
        symbolQuerying = false;
        symbolQueried = false;
        symbolValues = new HashMap<String, Long>();
        threadInfoSupport = false;
        resposneForMe = false;
        targetCurrentThreadUniqueID = -1;
        needReloadThreadInfo = true;
        
        return super.connect();
    }
    
    private boolean symbolQuery(Packet pkt)
    {
        if(symbolIndex > 0)
        {
            
            long value;
            String strValue;
            Scanner scanner = new Scanner(pkt.toString());
            scanner.useDelimiter(":");
            scanner.next();
            strValue = scanner.next();
            if(strValue.length() > 0)
            {
                value = Integer.parseInt(strValue, 16);
            }
            else
            {
                value = -1;
            }
            symbolValues.put(symbols[symbolIndex - 1], value);
            
            verbosePrint("SymbolQuery:%s:0x%08x\n", 
                    symbols[symbolIndex - 1], 
                    symbolValues.get(symbols[symbolIndex - 1]));
        }
        
        if(symbolIndex < symbols.length)
        {
            Packet normalPkt = Packet.makeNormalPacket("qSymbol:" + toHexEncodedString(symbols[symbolIndex]));
            symbolIndex++;
            return this.outputResponse(normalPkt);
        }
        else
        {
            symbolQuerying = false;
            symbolQueried = true;
            threadInfoSupport = true; // 首先假设支持线程调试
            for(int i = 0; i < symbols.length; i++)
            {
                if(symbolValues.get(symbols[i]) == -1)
                {
                    threadInfoSupport = false; // 发现未解析符号，那么不能正确获取线程信息，因此不支持多线程
                    System.err.printf("Multi thread debug is not support, because of unresolved symbol %s\n", symbols[i]);
                }
            }
            
            Packet normalPkt = Packet.makeNormalPacket("qSymbol::");
            return this.outputCommand(normalPkt);
        }
    }
    
    // TODO 需要一个更合理的方法名
    private Packet getResponse(Packet cmd)
    {
        Packet res = null;
        
        lock.lock();
        try
        {
            resposneForMe = true;
            responsePkt = null;
            this.outputCommand(cmd); // 发送命令
            while(responsePkt == null)
            {
                condition.await();// 等待响应, TODO 如果GDBServer挂机了，不会返回响应如何处理
            }
            res = responsePkt;
            resposneForMe = false;
        }
        catch(InterruptedException e)
        {
            e.printStackTrace();
        }
        finally
        {
            lock.unlock();
        }
        
        return res;
    }
    
    private eCosRegistersForThread readRegistersFromThreadStack(long stackPtr)
    {
        eCosRegistersForThread registers = new eCosRegistersForThread();
        Packet cmd = Packet.makeMemReadPacket(stackPtr, 68);
        Packet res = getResponse(cmd);
        registers.fromString(res.toString());
        
        return registers;
    }
    
    private eCosRegistersForJLink readRegistersFromCPU()
    {
        eCosRegistersForJLink registers = new eCosRegistersForJLink();
        Packet cmd = Packet.makeNormalPacket("g");
        Packet res = getResponse(cmd);
        registers.fromString(res.toString());
        
        return registers;
    }
    
    private String readStringFromTarget(long addr, int maxLength) throws IOException
    {
        Packet cmd = Packet.makeMemReadPacket(addr, maxLength);
        Packet res = getResponse(cmd);
        String value = res.parsePacketAsString();
        verbosePrint("*0x%08x = \"%s\"\n", addr, value);
        
        return value;
    }
    
    private int readHalfWordFromTarget(long addr) throws IOException
    {
        Packet cmd = Packet.makeMemReadPacket(addr, 2);
        Packet res = getResponse(cmd);
        int value = res.parsePacketAsHalfWord();
        verbosePrint("*0x%08x = 0x%04x\n", addr, value);
        
        return value;
    }
    
    private long readWordFromTarget(long addr) throws IOException
    {
        Packet cmd = Packet.makeMemReadPacket(addr, 4);
        Packet res = getResponse(cmd);
        long value = res.parsePacketAsWord();
        verbosePrint("*0x%08x = 0x%08x\n", addr, value);
        
        return value;
    }
    
    private void updateThreadListFromKernel(long threadFirst) throws IOException
    {
        long threadNext = threadFirst;
        do
        {
            eCosThreadInfo thread = new eCosThreadInfo();
            thread.namePtr = readWordFromTarget(threadNext + eCosThreadInfo.offsetNamePtr);
            thread.stackPtr = readWordFromTarget(threadNext + eCosThreadInfo.offsetStackPtr);
            thread.uniqueID = readHalfWordFromTarget(threadNext + eCosThreadInfo.offsetUniqueID);
            thread.state = readWordFromTarget(threadNext + eCosThreadInfo.offsetState);
            if(thread.namePtr != 0)
            {
                thread.name = readStringFromTarget(thread.namePtr, 64);
            }
            else
            {
                thread.name = "***NoName***";
            }
            threadMap.put(thread.uniqueID, thread);
            
            threadNext = readWordFromTarget(threadNext + eCosThreadInfo.offsetNextPtr);
        }while(threadNext != threadFirst);
        
        long threadCurrent = readWordFromTarget(
                symbolValues.get("Cyg_Scheduler_Base::current_thread"));
        targetCurrentThreadUniqueID = readHalfWordFromTarget(
                threadCurrent + eCosThreadInfo.offsetUniqueID);
    }
    
    private void createFakeThreadList()
    {
        eCosThreadInfo thread = new eCosThreadInfo();
        thread.name = "<Initialize environment>";
        thread.state = 0;
        thread.uniqueID = 65536; // 用超出unsigned short可表示范围的数值标识初始化运行环境
        eCosRegistersForJLink registers = readRegistersFromCPU();
        thread.stackPtr = registers.sp;
        threadMap.put(thread.uniqueID, thread);
        
        targetCurrentThreadUniqueID = thread.uniqueID;
    }
    
    private void updateThreadList()
    {
        // 仅在有需要的情况下更新线程信息，因为只有在执行了代码的情况下才会改变线程状态
        if(!needReloadThreadInfo)
        {
            return;
        }
        
        boolean kernelRunning = false;
        threadMap = new HashMap<Integer, eCosThreadInfo>();
        
        try
        {
            // 读取SysTick寄存器
            long systickCSR = readWordFromTarget(
                    CYGARC_REG_SYSTICK_BASE + CYGARC_REG_SYSTICK_CSR);
            // 读取Cyg_Thread::thread_list值
            long threadFirst = readWordFromTarget(
                    symbolValues.get("Cyg_Thread::thread_list"));
            if((systickCSR & CYGARC_REG_SYSTICK_CSR_ENABLE) == 0)
            {
                // 通过SysTick寄存器值判断是否已经初始化静态数据，如果静态数据未初始化，
                // 那么内核肯定还未初始化。
                kernelRunning = false;
                this.verbosePrint("SysTick is disable, so kernel is not running.\n");
            }
            else
            {
                // SysTick已经初始化，Cyg_Thread::thread_list是否为0，可以判断内核是否已初始化。
                if(threadFirst == 0)
                {
                    kernelRunning = false;
                    this.verbosePrint("SysTick is enable but Cyg_Thread::thread_list == 0, so kernel is not running.\n");
                }
                else
                {
                    // SysTick已初始化，Cyg_Thread::thread_list不为0，可以保证内核已经初始化。
                    this.verbosePrint("SysTick is enable and Cyg_Thread::thread_list != 0 == 0x%08x, so kernel is running.\n", threadFirst);
                    kernelRunning = true;
                }
            }
            
            if(kernelRunning)
            {
                // 内核已初始化，从内核读取线程信息
                updateThreadListFromKernel(threadFirst);
            }
            else
            {
                // 内核未初始化，构造伪线程信息
                createFakeThreadList();
            }
        }
        catch(IOException e)
        {
            // 如果发生异常，很可能还处于初始化状态，内核未完成初始化
            createFakeThreadList();
        }
        
        needReloadThreadInfo = false;
        
        verbosePrint("Target current thread is %d\n", 
                targetCurrentThreadUniqueID);
    }
    
    private boolean getThreadList(Packet pkt)
    {
        if(pkt.startsWith("qsThreadInfo"))
        {
            return this.outputResponse(Packet.makeNormalPacket("l"));
        }
        
        updateThreadList();
        
        String strThreadList = "m";
        for(Integer i: threadMap.keySet())
        {
            int id = threadMap.get(i).uniqueID;
            strThreadList += Integer.toHexString(id) + ",";
        }
        strThreadList = strThreadList.substring(0, strThreadList.length() - 1);
        
        Packet respoPkt = Packet.makeNormalPacket(strThreadList);
        return this.outputResponse(respoPkt);
    }
    
    private boolean getThreadInfo(Packet pkt)
    {
        Scanner scanner = new Scanner(pkt.toString());
        scanner.useDelimiter(",");
        scanner.next();
        int threadID = (int)scanner.nextLong(16);
        if((threadID == 0xffffffff) || (threadID == 0))
        {
            threadID = this.targetCurrentThreadUniqueID;
        }
        
        eCosThreadInfo threadInfo = threadMap.get(threadID);
        String strState;
        if((eCosThreadInfo.State.parse(threadInfo.state) == eCosThreadInfo.State.RUNNING)
                && (threadID != this.targetCurrentThreadUniqueID))
        {
            strState = "READY";
        }
        else
        {
            strState = eCosThreadInfo.State.parse(threadInfo.state).toString();
        }
        String str = String.format("name:%s; state:%s", 
                threadInfo.name, strState);
        
        return this.outputResponse(Packet.makeNormalPacket(toHexEncodedString(str)));
    }
    
    private boolean getThreadRegisters(Packet pkt)
    {
        eCosRegistersForJLink regJLink;
        eCosRegistersForThread regThread;
        
        regJLink = this.readRegistersFromCPU();
        regThread = this.readRegistersFromThreadStack(
                this.threadMap.get(selectCurrentThreadUniqueID).stackPtr);
        
        for(int i = 0; i < 13; i++)
        {
            regJLink.r0_r12[i] = regThread.r0_r12[i];
        }
        regJLink.basepri = regThread.basepri;
        regJLink.sp = regThread.sp;
        regJLink.lr = regThread.pc;
        regJLink.pc = regThread.pc;
        
        return this.outputResponse(Packet.makeNormalPacket(regJLink.toString()));
    }
    
    private boolean checkThreadAlive(Packet pkt)
    {
        updateThreadList();
        
        String str;
        int threadID = Integer.parseInt(pkt.toString().substring(1), 16);
        if(threadMap.containsKey(threadID))
        {
            str = "OK";
        }
        else
        {
            str = "E00";
        }
        
        Packet respoPkt = Packet.makeNormalPacket(str);
        return this.outputResponse(respoPkt);
    }
    
    public boolean inputCommand(Packet pkt)
    {
        if(symbolQuerying)
        {
            return symbolQuery(pkt);
        }
        
        if((!symbolQueried) && pkt.startsWith("qSymbol"))
        {
            symbolQuerying = true;
            return symbolQuery(pkt);
        }
        
        if(pkt.startsWith("qSymbol"))
        {
            Packet normalPkt = Packet.makeNormalPacket("OK");
            return this.outputResponse(normalPkt);
        }
        
        if(threadInfoSupport)
        {
            if(pkt.startsWith("qfThreadInfo"))
            {
                return getThreadList(pkt);
            }
            else if(pkt.startsWith("qsThreadInfo"))
            {
                return getThreadList(pkt);
            }
            else if(pkt.startsWith("qThreadExtraInfo"))
            {
                return getThreadInfo(pkt);
            }
            else if(pkt.startsWith("g"))
            {
                if((this.selectCurrentThreadUniqueID > 0) 
                        && (this.selectCurrentThreadUniqueID != this.targetCurrentThreadUniqueID))
                {
                    return getThreadRegisters(pkt);
                }
            }
            else if(pkt.startsWith("T"))
            {
                return checkThreadAlive(pkt);
            }
            else
            {
                
            }
            
            if(pkt.startsWith("s")
                    || pkt.startsWith("c")
                    || pkt.startsWith("vCont")
                    || pkt.startsWith("i")
                    || pkt.startsWith("r")
                    || pkt.startsWith("C")
                    || pkt.startsWith("S")
                    || pkt.startsWith("I")
                    || pkt.startsWith("R")
                    || pkt.startsWith("bc")
                    || pkt.startsWith("bs")
                    || pkt.startsWith("k"))
            {
                needReloadThreadInfo = true;
            }
        }
        
        if(pkt.startsWith("H"))
        {
            this.selectCurrentThreadUniqueID = Integer.parseInt(
                    pkt.toString().substring(2), 16);
            verbosePrint("Select Thread %d as current operate thread.\n", 
                    this.selectCurrentThreadUniqueID);
            return this.outputResponse(Packet.makeNormalPacket("OK"));
        }

        return this.outputCommand(pkt);
    }
    
    public boolean inputResponse(Packet pkt)
    {
        if(resposneForMe)
        {
            lock.lock();
            responsePkt = pkt;
            condition.signalAll();
            lock.unlock();
            
            return true;
        }
        else
        {
            return this.outputResponse(pkt);
        }
    }
    
    private String toHexEncodedString(String str)
    {
        byte[] strByte = str.getBytes();
        String encodedStr = "";
        
        for(byte b: strByte)
        {
            encodedStr += Integer.toHexString(b);
        }
        
        return encodedStr;
    }
    
    private void verbosePrint(String fmt, Object...objects)
    {
        if(this.verbose)
        {
            System.out.printf(fmt, objects);
        }
    }
    
    final private String[] symbols = {"Cyg_Thread::thread_list", "Cyg_Scheduler_Base::current_thread"};
    private HashMap<String, Long> symbolValues;
    private int symbolIndex;
    private boolean symbolQuerying;
    private boolean symbolQueried;
    private boolean verbose;
    private boolean threadInfoSupport;
    private volatile boolean resposneForMe;
    private volatile Packet responsePkt;
    private Lock lock;
    private Condition condition;
    private HashMap<Integer, eCosThreadInfo> threadMap;
    private int targetCurrentThreadUniqueID; // 目标机上的当前线程
    private int selectCurrentThreadUniqueID; // GDB选择的操作线程
    private boolean needReloadThreadInfo;
    
    private static final long CYGARC_REG_SYSTICK_BASE = 0xE000E010;
    private static final long CYGARC_REG_SYSTICK_CSR  = 0;
    private static final long CYGARC_REG_SYSTICK_CSR_ENABLE = 1;
}

class eCosThreadInfo
{
    public long stackPtr;
    public long namePtr;
    public long state;
    public int uniqueID;
    public String name;
    
    static public long offsetStackPtr = 0x0c;
    static public long offsetNamePtr = 0x9c;
    static public long offsetNextPtr = 0xa0;
    static public long offsetState = 0x3c;
    static public long offsetUniqueID = 0x4c;
    
    public enum State
    {
        RUNNING(0),     // Thread is runnable or running
        SLEEPING(1),    // Thread is waiting for something to happen
        COUNTSLEEP(2),  // Sleep in counted manner
        SUSPENDED(4),   // Suspend count is non-zero
        CREATING(8),    // Thread is being created
        EXITED(16);     // Thread has exited
        
        State(int mask)
        {
            this.mask = mask;
        }
        public int mask;
        
        static public State parse(long state)
        {
            if((state & SLEEPING.mask) > 0)
            {
                return SLEEPING;
            }
            
            if((state & COUNTSLEEP.mask) > 0)
            {
                return COUNTSLEEP;
            }
            
            if((state & SUSPENDED.mask) > 0)
            {
                return SUSPENDED;
            }
            
            if((state & CREATING.mask) > 0)
            {
                return CREATING;
            }
            
            if((state & EXITED.mask) > 0)
            {
                return EXITED;
            }
            
            return RUNNING;
        }
    }
}

class eCosRegistersForJLink
{
    public eCosRegistersForJLink()
    {
        r0_r12 = new long[13];
    }
    
    public String toString()
    {
        String str = "";
        
        for(int i = 0; i < 13; i++)
        {
            str +=String.format("%08x", r0_r12[i]);
        }
        str += String.format("%08x", sp);
        str += String.format("%08x", lr);
        str += String.format("%08x", pc);
        str += String.format("%08x", xpsr);
        str += String.format("%08x", msp);
        str += String.format("%08x", psp);
        str += String.format("%08x", primask);
        str += String.format("%08x", basepri);
        str += String.format("%08x", faultmask);
        str += String.format("%08x", control);
        
        return str;
    }
    
    public void fromString(String str)
    {
        int i;
        for(i = 0; i < 13; i++)
        {
            r0_r12[i] = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        }
        i = 13;
        sp = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        lr = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        pc = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        xpsr = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        msp = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        psp = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        primask = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        basepri = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        faultmask = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        control = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
    }
    
//    private long swap(long n)
//    {
//        long out = (((n >> 0) & 0xff) << 24) // 字节次序交换
//                 | (((n >> 8) & 0xff) << 16) 
//                 | (((n >> 16) & 0xff) << 8) 
//                 | (((n >> 24) & 0xff) << 0);
//        
//        return out;
//    }
    
    public long[] r0_r12;
    public long sp;
    public long lr;
    public long pc;
    public long xpsr;
    public long msp;
    public long psp;
    public long primask;
    public long basepri;
    public long faultmask;
    public long control;
}


class eCosRegistersForThread
{
    public eCosRegistersForThread()
    {
        r0_r12 = new long[13];
    }
    
    public void fromString(String str)
    {
        int i = 0;
        
        type = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        basepri = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        sp = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        i++;
        
        for(; i < 13 + 3; i++)
        {
            r0_r12[i - 3] = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
        }
        pc = Long.parseLong(str.substring(i * 8, i * 8 + 8), 16);
    }
    
    public long type;
    public long basepri;
    public long sp;
    public long[] r0_r12;
    public long pc;
}
